import asyncio
import json
from typing import List, Dict
import aioredis
from datetime import datetime
from core.config import settings
from core.llm import get_default_llm
import logging

logger = logging.getLogger(__name__)


class ChatService:
    def __init__(self):
        self._redis_client = None
        self.llm = get_default_llm()
        self.MAX_HISTORY_LENGTH = 100
        self.redis_config = {
            "address": (settings.REDIS_HOST, settings.REDIS_PORT),
            "db": settings.REDIS_DB,
            "encoding": "utf-8",
            # 直接取配置的密码，空值时为""或None
            "password": settings.REDIS_PASSWORD
        }

    # ------------------------------
    # 核心：异步属性封装，强制所有Redis访问先初始化
    # ------------------------------
    @property
    async def redis_client(self):
        """异步属性：获取Redis客户端前，确保已初始化"""
        if self._redis_client is None:
            await self._init_redis()  # 调用私有初始化方法
        return self._redis_client

    async def _init_redis(self):
        """私有异步初始化：动态处理密码参数，空密码时不传递"""
        if self._redis_client is not None:
            return
        try:
            # 1. 复制基础配置（避免修改原字典）
            redis_config = self.redis_config.copy()
            # 2. 关键：若密码为空字符串/None，删除password参数
            if redis_config.get("password") in ("", None):
                redis_config.pop("password", None)  # 安全删除，避免KeyError

            # 3. 初始化Redis连接池（此时无password参数，不发送AUTH命令）
            self._redis_client = await aioredis.create_redis_pool(
                **redis_config
            )
            logger.info(
                f"Redis客户端初始化成功（连接：{self.redis_config['address'][0]}:{self.redis_config['address'][1]}，db={self.redis_config['db']}）")
        except Exception as e:
            logger.error(
                f"Redis客户端初始化失败：{str(e)} "
                f"（参数：host={self.redis_config['address'][0]}, port={self.redis_config['address'][1]}, db={self.redis_config['db']}，是否传密码：{('password' in redis_config)}）",
                exc_info=True
            )
            raise

    # ------------------------------
    # 辅助方法：生成Redis键（避免硬编码）
    # ------------------------------
    def _get_chat_key(self, account_no: str) -> str:
        """生成对话历史存储键
        
        Args:
            account_no: 用户账号
            
        Returns:
            Redis键名
        """
        return f"maxkb_chat:{account_no}"

    def _get_summary_key(self, account_no: str) -> str:
        """生成对话摘要存储键
        
        Args:
            account_no: 用户账号
            
        Returns:
            Redis键名
        """
        return f"chat_summary:{account_no}"

    def _get_summary_idx_key(self, account_no: str) -> str:
        """生成摘要位置索引键（记录上次摘要的历史长度）
        
        Args:
            account_no: 用户账号
            
        Returns:
            Redis键名
        """
        return f"chat_summary_idx:{account_no}"

    # ------------------------------
    # 对话历史核心方法（所有Redis操作均通过异步属性）
    # ------------------------------
    async def get_chat_history(self, account_no: str, limit: int = 20) -> List[Dict[str, str]]:
        """获取用户对话历史
        
        Args:
            account_no: 用户账号
            limit: 返回消息的最大数量，默认为20条
            chat_id: 可选的聊天ID
            
        Returns:
            消息列表
        """
        logger.debug(f"[DEBUG] get_chat_history开始 - 用户：{account_no}，限制：{limit}")
        
        try:
            # 关键：通过异步属性获取客户端（确保已初始化）
            redis_cli = await self.redis_client
            logger.debug(f"[DEBUG] Redis客户端获取成功")
            
            key = self._get_chat_key(account_no)
            logger.debug(f"[DEBUG] 生成的Redis键：{key}")
            
            # 检查键是否存在
            key_exists = await redis_cli.exists(key)
            logger.debug(f"[DEBUG] 键是否存在：{key_exists}")
            
            if not key_exists:
                logger.debug(f"[DEBUG] 键不存在，返回空列表")
                return []
            
            # 检查列表长度
            list_length = await redis_cli.llen(key)
            logger.debug(f"[DEBUG] 列表长度：{list_length}")
            
            # 修复：使用lrange(0, limit-1)获取最新的limit条消息
            # 由于我们使用lpush添加消息，最新的消息在列表前面
            history_strs = await redis_cli.lrange(key, 0, limit - 1)
            logger.debug(f"[DEBUG] LRANGE返回的消息数量：{len(history_strs)}")
            
            if not history_strs:
                logger.debug(f"[DEBUG] 历史记录为空")
                return []
            
            # 反序列化每条消息
            result = []
            for msg_str in history_strs:
                try:
                    msg = json.loads(msg_str)
                    result.append(msg)
                    logger.debug(f"[DEBUG] 成功解析消息：{msg.get('id')}, {msg.get('role')}")
                except json.JSONDecodeError as e:
                    logger.error(f"[DEBUG] 消息解析失败：{msg_str[:50]}...，错误：{str(e)}")
                    continue
            
            # 添加明确的聊天记录条数日志
            logger.info(f"[INFO] 获取聊天记录成功 - 用户：{account_no}，获取到的记录条数：{len(result)}")
            logger.debug(f"[DEBUG] get_chat_history完成 - 返回消息数量：{len(result)}")
            return result
        except Exception as e:
            logger.error(f"[DEBUG] get_chat_history异常：{str(e)}", exc_info=True)
            return []

    async def add_message(self, account_no: str, content: str, role: str) -> bool:
        """追加单条消息到对话历史（支持 user/assistant 角色）"""
        logger.debug(f"[DEBUG] add_message开始 - 用户：{account_no}，角色：{role}")
        
        # 参数验证
        if not account_no or not content or role not in ["user", "assistant"]:
            logger.error(f"[DEBUG] 参数验证失败 - account_no: {bool(account_no)}, content: {bool(content)}, role: {role}")
            return False
        
        try:
            # 获取Redis客户端
            redis_cli = await self.redis_client
            logger.debug(f"[DEBUG] Redis客户端获取成功")
            
            # 生成键名（确保与get_chat_history使用相同的key生成逻辑）
            key = self._get_chat_key(account_no)
            logger.debug(f"[DEBUG] 生成的Redis键：{key}")
            
            # 构造消息字典（包含唯一ID和时间戳）
            new_msg = {
                "id": f"{account_no}:{datetime.now().timestamp()}:{hash(content[:20])}",  # 增强的唯一ID
                "content": content.strip(),  # 清理空白字符
                "role": role.lower(),  # 统一角色为小写
                "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]  # 精确时间戳
            }
            logger.debug(f"[DEBUG] 构造的消息：{new_msg}")
            
            try:
                # 序列化消息
                serialized_msg = json.dumps(new_msg, ensure_ascii=False)
                logger.debug(f"[DEBUG] 序列化后的消息：{serialized_msg[:100]}...")  # 限制日志长度
            except Exception as e:
                logger.error(f"[DEBUG] 消息序列化失败：{str(e)}")
                return False
            
            # 使用事务确保原子性（不使用异步上下文管理器）
            pipe = redis_cli.pipeline()
            # 追加到Redis List（lpush：最新消息在最前）
            # 这样可以确保最新的消息在列表头部，与get_chat_history的读取逻辑一致
            pipe.lpush(key, serialized_msg)
            # 截断列表：仅保留最新的 MAX_HISTORY_LENGTH 条消息
            pipe.ltrim(key, 0, self.MAX_HISTORY_LENGTH - 1)
            # 移除过期时间设置，确保聊天记录永久存储
            # 执行事务
            await pipe.execute()
            
            # 验证消息是否成功添加
            list_length = await redis_cli.llen(key)
            logger.debug(f"[DEBUG] add_message成功完成 - 用户：{account_no}，角色：{role}，当前列表长度：{list_length}")
            return True
        except Exception as e:
            logger.error(f"[DEBUG] 追加消息失败（用户：{account_no}，角色：{role}）：{str(e)}", exc_info=True)
            return False

    async def save_chat_message(self, account_no: str, user_msg: str, assistant_msg: str) -> bool:
        """批量保存一次完整对话（用户消息 + 助手消息）"""
        logger.debug(f"[DEBUG] save_chat_message开始 - 用户：{account_no}")
        logger.debug(f"[DEBUG] 用户消息：{user_msg[:50]}...")  # 只记录前50个字符
        logger.debug(f"[DEBUG] 助手消息：{assistant_msg[:50]}...")  # 只记录前50个字符
        
        try:
            # 并行执行两条消息的追加（用 asyncio.gather 提高效率）
            logger.debug(f"[DEBUG] 开始并行添加两条消息")
            user_result, assistant_result = await asyncio.gather(
                self.add_message(account_no, user_msg, "user"),
                self.add_message(account_no, assistant_msg, "assistant")
            )
            
            logger.debug(f"[DEBUG] 并行操作完成 - 用户消息保存结果：{user_result}，助手消息保存结果：{assistant_result}")
            
            # 只有两条消息都成功才返回True
            final_result = user_result and assistant_result
            logger.debug(f"[DEBUG] save_chat_message完成 - 最终结果：{final_result}")
            
            return final_result
        except Exception as e:
            logger.error(f"[DEBUG] save_chat_message异常：{str(e)}", exc_info=True)
            return False

    async def clear_chat_history(self, account_no: str) -> bool:
        """清空用户对话相关数据（历史、摘要、索引）
        
        Args:
            account_no: 用户账号
            
        Returns:
            是否成功删除
        """
        try:
            redis_cli = await self.redis_client
            
            # 先尝试模式匹配获取所有相关键（包含可能的旧格式键）
            # 匹配两种格式：chat_*和maxkb_chat格式
            patterns = [f"chat_*:{account_no}", f"maxkb_chat:{account_no}"]
            matched_keys = []
            for pattern in patterns:
                matched_keys.extend(await redis_cli.keys(pattern))
            # 添加默认的用户键（统一使用不带chat_id的格式）
            default_keys = [
                self._get_chat_key(account_no),
                self._get_summary_key(account_no),
                self._get_summary_idx_key(account_no)
            ]
            # 合并并去重
            keys = list(set(default_keys + matched_keys))
            logger.info(f"正在清空用户 {account_no} 的所有对话数据（键数量：{len(keys)}）")
            
            if keys:
                # 创建pipeline（不使用异步上下文管理器）
                pipe = redis_cli.pipeline()
                # 批量删除键（避免多次Redis交互）
                pipe.delete(*keys)
                # 执行事务
                execute_result = await pipe.execute()
                result = execute_result[0]  # 获取delete操作的结果
                logger.info(f"删除完成，成功删除 {result} 个键")
            else:
                logger.info(f"没有找到需要删除的键")
            return True
        except Exception as e:
            logger.error(f"清空聊天记录失败（用户：{account_no}）：{str(e)}", exc_info=True)
            return False

    # ------------------------------
    # 对话摘要生成方法（增量生成，减少LLM计算量）
    # ------------------------------
    async def generate_summary(self, account_no: str) -> str:
        """增量生成对话摘要（仅处理新增消息，避免重复计算）"""
        redis_cli = await self.redis_client
        # 1. 获取当前对话历史和历史长度
        history = await self.get_chat_history(account_no, limit=100)
        if not history:
            logger.warning(f"用户 {account_no} 无对话历史，无需生成摘要")
            return ""
        current_history_len = len(history)

        # 2. 获取上次生成摘要时的历史长度（无则默认为0）
        idx_key = self._get_summary_idx_key(account_no)
        last_summary_len = await redis_cli.get(idx_key)
        last_summary_len = int(last_summary_len) if last_summary_len else 0

        # 3. 无新增消息：直接返回旧摘要
        if current_history_len <= last_summary_len:
            old_summary = await redis_cli.get(self._get_summary_key(account_no))
            return old_summary.strip() if old_summary else ""

        # 4. 提取新增消息（仅处理上次摘要后的数据）
        new_messages = history[last_summary_len:]  # 切片：从上次长度到最新
        # 获取旧摘要（无则用默认文本）
        old_summary = await redis_cli.get(self._get_summary_key(account_no)) or "该用户此前无对话记录"

        # 5. 构造LLM提示词（精简：仅含新增消息+旧摘要）
        prompt = f"""
        任务：基于用户的旧对话摘要和新增对话，生成更新后的完整摘要。
        旧摘要：{old_summary}
        新增对话（按时间顺序，最新在后）：{json.dumps(new_messages, ensure_ascii=False, indent=2)}

        生成要求：
        1. 保留所有关键信息（如问题、答案、数据、待办事项）；
        2. 突出新增内容，弱化重复的旧信息；
        3. 用第三人称客观描述，语言简洁（不超过300字）；
        4. 时间戳仅保留关键节点（如“2025-10-01 用户询问Kafka...”）。
        """

        try:
            # 调用LLM生成新摘要（假设 get_default_llm() 返回异步LLM实例）
            response = await self.llm.ainvoke(prompt)
            new_summary = response.content.strip()

            # 6. 保存新摘要和当前历史长度（更新索引）
            await asyncio.gather(
                redis_cli.set(self._get_summary_key(account_no), new_summary),
                redis_cli.set(idx_key, str(current_history_len))
            )
            logger.info(f"生成摘要成功（用户：{account_no}，新增消息数：{len(new_messages)}）")
            return new_summary
        except Exception as e:
            logger.error(f"生成摘要失败（用户：{account_no}）：{str(e)}", exc_info=True)
            return old_summary  # 失败时返回旧摘要，避免影响用户体验